#include "PPU.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

////////////////////////////////////////////////////////////////////////////////////////////////////

// scanlines
#define PRE_RENDER_SCANLINE     -1
#define LAST_VISIBLE_SCANLINE   239
#define POST_RENDER_SCANLINE    240
#define LAST_VBLANK_SCANLINE    260
#define LAST_SCANLINE           261

////////////////////////////////////////////////////////////////////////////////////////////////////

PPU::PPU() {
    m_Bus = 0;
    m_Buffer = 0;
    m_Latch = true;
    m_Reg.R0 = 0;
    m_Reg.R1 = 0;
    m_Reg.R2 = 0;
    m_Reg.R3 = 0;
    m_Reg.R4 = 0;
    m_Reg.R5 = 0;
    m_Reg.R6 = 0;
    m_Reg.R7 = 0;
    m_Scanline = -1;
    m_VBlank = false;
    m_SpriteOverflow = false;
    m_NMI = false;
    m_CanRender = false;
    m_LoopyT = 0;
    m_ScreenData = ( unsigned long* )malloc( 256 * 240 * sizeof(unsigned long) );
    memset( m_ScreenData, 0, 256 * 240 * sizeof(unsigned long) );
    m_CurrentCycle = 0;
    m_EndCycle = 341;
    m_IsShortScanline = false;
    m_RenderPixel = false;
    m_Tile = 0;
    m_PatternTableAddress = 0;
    m_AttributeAddress = 0;
    m_AttributeShift = 0;
    m_AttributeByte = 0;
    m_AttributeMask = 0;
    m_ColorIndex = ( unsigned char* )malloc( 272 );
    memset( m_ColorIndex, 0, 272 );
    m_RenderAddress = 0;
    m_CurrentColorIndex = 0;
    m_FineX = 0;
    m_PixelCounter = 0;
    m_IsRendering = false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

PPU::~PPU() {}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool PPU::WantNMI() {
    bool t = m_NMI;
    if( m_NMI ) m_NMI = false;

    return t;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::ReadRegister( int reg ) {
    switch( reg ) {
        case 0: {
            return m_Bus;
        } break;

        case 1: {
            return m_Bus;
        } break;

        case 2: {
            return Read2002();
        } break;

        case 3: {
            return m_Bus;
        } break;

        case 4: {
            return Read2004();
        } break;

        case 5: {
            return m_Bus;
        } break;

        case 6: {
            return m_Bus;
        } break;

        case 7: {
            return Read2007();
        } break;

        default: {
            printf( "\nERROR: < %i > no es un registro de PPU valido (lectura)", reg );
            getchar();
            exit( 0 );
        } break;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::WriteRegister( int reg, unsigned char value ) {
    switch( reg ) {
        // TODO
        case 0: {
            m_Bus = value;
            Write2000( value );
        } break;

        case 1: {
            Write2001( value );
        } break;

        case 2: {
            m_Bus = value;
        } break;

        case 3: {
            m_Bus = value;
            Write2003( value );
        } break;

        case 4: {
            m_Bus = value;
            Write2003( value );
        } break;

        case 5: {
            m_Bus = value;
            Write2005( value );
        } break;

        case 6: {
            m_Bus = value;
            Write2006( value );
        } break;

        case 7: {
            m_Bus = value;
            Write2007( value );
        } break;

        default: {
            printf( "\nERROR: < %i > no es un registro de PPU valido (escritura)", reg );
            getchar();
            exit( 0 );
        } break;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::Read2002() {
    unsigned char t = m_Reg.R2;

    //if( m_VBlank ) t |= 0x80;
    //if( m_SpriteOverflow ) t |= 0x20;

    m_Reg.R2 &= 0x7f;
    m_Latch = true;

    return t;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::Read2004() {
    // FIXME: no se si esto es asi o al reves, o si es del todo correcto...
    //if( m_VBlank ) {
    if( m_IsRendering ) {
        return m_Reg.R3;
    } else {
        return m_SRAM[ m_Reg.R3 ];
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

unsigned char PPU::Read2007() {
    unsigned char t = 0;

    // la direccion corresponde a la paleta
    if( m_Reg.R6 < 0x3f00 ) {
        t = m_Buffer;
        m_Buffer = m_VRAM[ m_Reg.R6 ];
    }

    // la direccion no corresponde a la paleta
    else if( m_Reg.R6 < 0x4000 ) {
        t = m_VRAM[ m_Reg.R6 ];
        m_Buffer = m_VRAM[ (m_Reg.R6 - 0x1000) & 0xffff ];
    }

    m_Reg.R6 += ( TestBit(m_Reg.R0, 2) ? 32 : 1 );

    return t;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2000( unsigned char value ) {
    m_Reg.R0 = value;
    m_LoopyT &= 0x73ff;
    m_LoopyT |= ( (value & 0x03) << 10 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2001( unsigned char value ) {
    m_Reg.R1 = value;
    m_IsRendering = ( (TestBit(value, 3) || TestBit(value, 4)) && (m_Scanline < 240) );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2003( unsigned char value ) {
    m_Reg.R3 = value;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2004( unsigned char value ) {
    // FIXME: puede que el valor de OAMADDR no se modifique si se escribe cuando la PPU esta renderizando
    m_SRAM[ m_Reg.R3++ ] = value;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2005( unsigned char value ) {
    if( m_Latch ) {
        m_LoopyT &= 0x7fe0;
        m_LoopyT |= ( (value & 0xf8) >> 3 );
        m_FineX = ( value & 0x07 );
    } else {
        m_LoopyT &= 0x0c1f;
        m_LoopyT |= ( ((value & 0xf8) << 2) | ((value & 0x07) << 12) );
    }
    m_Latch ^= true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2006( unsigned char value ) {
    if( m_Latch ) {
        m_LoopyT &= 0x00ff;
        m_LoopyT |= ( (value & 0x3f) << 8 );
    } else {
        m_LoopyT &= 0x7f00;
        m_LoopyT |= value;
        m_Reg.R6 = m_LoopyT;
    }
    m_Latch ^= true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Write2007( unsigned char value ) {
    // name tables
    if( m_Reg.R6 < 0x3000 ) {
        // TODO: mappers
        if( m_Reg.R6 >= 0x2000 ) {
            unsigned short a = ( m_Reg.R6 & 0x2c00 );

            // mirroring vertical
            if( m_Mirroring == VERTICAL ) {
                if( a == 0x2000 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0800) & 0xffff ] = value;
                } else if( a == 0x2400 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0800) & 0xffff ] = value;
                } else if( a == 0x2800 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0800) & 0xffff ] = value;
                } else if( a == 0x2c00 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0800) & 0xffff ] = value;
                }
            }

            // mirroring horizontal
            else if( m_Mirroring == HORIZONTAL ) {
                if( a == 0x2000 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0400) & 0xffff ] = value;
                } else if( a == 0x2400 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0400) & 0xffff ] = value;
                } else if( a == 0x2800 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 + 0x0400) & 0xffff ] = value;
                } else if( a == 0x2c00 ) {
                    m_VRAM[ m_Reg.R6 ] = value; m_VRAM[ (m_Reg.R6 - 0x0400) & 0xffff ] = value;
                }
            }
        }
    }

    
    // paleta
    else if( m_Reg.R6 < 0x4000 ) {
        value &= 0x3f;
        m_VRAM[ m_Reg.R6 ] = value;
        if( (m_Reg.R6 == 0x3f00) || (m_Reg.R6 == 0x3f10) ) {
            m_VRAM[ 0x3f00 ] = value;
            m_VRAM[ 0x3f10 ] = value;
        } else if( (m_Reg.R6 == 0x3f04) || (m_Reg.R6 == 0x3f14) ) {
            m_VRAM[ 0x3f04 ] = value;
            m_VRAM[ 0x3f14 ] = value;
        } else if( (m_Reg.R6 == 0x3f08) || (m_Reg.R6 == 0x3f18) ) {
            m_VRAM[ 0x3f08 ] = value;
            m_VRAM[ 0x3f18 ] = value;
        } else if( (m_Reg.R6 == 0x3f0c) || (m_Reg.R6 == 0x3f1c) ) {
            m_VRAM[ 0x3f0c ] = value;
            m_VRAM[ 0x3f1c ] = value;
        }
    }

    m_Reg.R6 += ( TestBit(m_Reg.R0, 2) ? 32 : 1 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::WriteSpriteDMA( std::list<unsigned char> data ) {
    unsigned char address = 0;
    for( std::list<unsigned char>::iterator value = data.begin(); value != data.end(); value++ ) m_SRAM[ address++ ] = ( *value );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::RaiseInterrupt( Interrupt i ) {
    if( i == NMI ) m_NMI = true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool PPU::WantRender() {
    bool t = m_CanRender;
    if( m_CanRender ) m_CanRender = false;
    
    return t;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void PPU::Run( int cycles ) {
    for( int c = 0; c != cycles; ++c ) {
        m_CurrentCycle++;

        if( m_CurrentCycle == 256 ) {
            if( m_Scanline < 240 ) { /* nada todavia... */ }
            if( m_Scanline == -1 ) {
                if( (m_IsShortScanline) && m_IsRendering ) m_EndCycle = 340;
				else m_EndCycle = 341;
			} else m_EndCycle = 341;
        } else if( m_CurrentCycle == 304 ) {
            if( (m_Scanline == -1) && m_IsRendering ) m_Reg.R6 = m_LoopyT;
        } else if( m_CurrentCycle == m_EndCycle ) {
            m_CurrentCycle = 0;
            m_Scanline++;
            //m_PixelCounter = 0;
            if( m_PixelCounter >= 256 * 240 ) m_PixelCounter = 0;
            if( m_Scanline == 0 ) m_RenderPixel = true;
            else if( m_Scanline == 240 ) {
                m_IsRendering = false;
                m_RenderPixel = false;
            } else if( m_Scanline == 241 ) {
                //m_VBlank = true;
                m_CanRender = true;
                m_Reg.R2 |= 0x80;
                if( TestBit(m_Reg.R0, 7) ) RaiseInterrupt( NMI );
                m_Reg.R3 = 0;
            } else if( m_Scanline == 261 ) {
                m_Scanline = -1;
                m_IsShortScanline ^= true;
                m_Reg.R2 = 0;
                //m_VBlank = false;
                m_IsRendering = ( TestBit(m_Reg.R1, 3) || TestBit(m_Reg.R1, 4) );
            }
        }
        //else if( (m_Scanline == PRE_RENDER_SCANLINE) && (m_CurrentCycle == 1) ) m_Reg.R2 = 0; //m_VBlank = false;

        if( m_IsRendering ) {
            switch( m_CurrentCycle ) {
                case   0: case   8: case  16: case  24: case  32: case  40: case  48: case  56: case  64: case  72: case  80: case  88: case  96:
                case 104: case 112: case 120: case 128: case 136: case 144: case 152: case 160: case 168: case 176: case 184: case 192: case 200:
                case 208: case 216: case 224: case 232: case 240: case 248: case 320: case 328: {
                    m_Tile = m_VRAM[ 0x2000 | (m_Reg.R6 & 0x0fff) ];
                } break;

                case   1: case   9: case  17: case  25: case  33: case  41: case  49: case  57: case  65: case  73: case  81: case  89: case  97:
                case 105: case 113: case 121: case 129: case 137: case 145: case 153: case 161: case 169: case 177: case 185: case 193: case 201:
                case 209: case 217: case 225: case 233: case 241: case 249: case 321: case 329: {
                    m_PatternTableAddress = ( (m_Tile << 4) | (m_Reg.R6 >> 12) | ((TestBit(m_Reg.R0, 4) ? 1 : 0) << 12) );
                } break;

                case   2: case  10: case  18: case  26: case  34: case  42: case  50: case  58: case  66: case  74: case  82: case  90: case  98:
                case 106: case 114: case 122: case 130: case 138: case 146: case 154: case 162: case 170: case 178: case 186: case 194: case 202:
                case 210: case 218: case 226: case 234: case 242: case 250: {
                    m_AttributeAddress = ( 0x23c0 | (m_Reg.R6 & 0x0c00) | ATTRIBUTE_LOCK[m_Reg.R6 & 0x03ff] );
                } break;

                case   3: case  11: case  19: case  27: case  35: case  43: case  51: case  59: case  67: case  75: case  83: case  91: case  99:
                case 107: case 115: case 123: case 131: case 139: case 147: case 155: case 163: case 171: case 179: case 187: case 195: case 203:
                case 211: case 219: case 227: case 235: case 243: case 323: case 331: {
                    m_RenderAddress = m_AttributeAddress;
                    m_AttributeByte = m_VRAM[ m_AttributeAddress ];
                    m_AttributeByte &= m_AttributeMask;
                    m_AttributeByte >>= m_AttributeShift;
                    unsigned char color = ( (m_AttributeByte & 0x03) << 2 );
                    int index = ( ((m_CurrentCycle == 323) || (m_CurrentCycle == 331)) ? m_CurrentCycle - 323 : m_CurrentCycle + 13 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ index + k ] = color;
                    if( (m_Reg.R6 & 0x001f) == 0x001f ) m_Reg.R6 ^= 0x041f;
                    else                                m_Reg.R6++;
                } break;

                case   4: case  12: case  20: case  28: case  36: case  44: case  52: case  60: case  68: case  76: case  84: case  92: case 100:
                case 108: case 116: case 124: case 132: case 140: case 148: case 156: case 164: case 172: case 180: case 188: case 196: case 204:
                case 212: case 220: case 228: case 236: case 244: case 252: case 324: case 332: {
                    m_RenderAddress = m_PatternTableAddress;
                } break;

                case   5: case  13: case  21: case  29: case  37: case  45: case  53: case  61: case  69: case  77: case  85: case  93: case 101:
                case 109: case 117: case 125: case 133: case 141: case 149: case 157: case 165: case 173: case 181: case 189: case 197: case 205:
                case 213: case 221: case 229: case 237: case 245: case 253: case 325: case 333: {
                    int index = ( ((m_CurrentCycle == 325) || (m_CurrentCycle == 333)) ? m_CurrentCycle - 325 : m_CurrentCycle + 11 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ index + k ] |= ( (m_VRAM[m_PatternTableAddress] >> (k ^ 7)) & 0x01 );
                } break;

                case   6: case  14: case  22: case  30: case  38: case  46: case  54: case  62: case  70: case  78: case  86: case  94: case 102:
                case 110: case 118: case 126: case 134: case 142: case 150: case 158: case 166: case 174: case 182: case 190: case 198: case 206:
                case 214: case 222: case 230: case 238: case 246: case 254: case 326:case 334: {
                    m_RenderAddress = ( m_PatternTableAddress | 8 );
                } break;

                case   7: case  15: case  23: case  31: case  39: case  47: case  55: case  63: case  71: case  79: case  87: case  95: case 103:
                case 111: case 119: case 127: case 135: case 143: case 151: case 159: case 167: case 175: case 183: case 191: case 199: case 207:
                case 215: case 223: case 231: case 239: case 247: case 255: case 327: case 335: {
                    int index = ( ((m_CurrentCycle == 327) || (m_CurrentCycle == 335)) ? m_CurrentCycle - 327 : m_CurrentCycle + 9 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ index + k ] |= ( ((m_VRAM[m_PatternTableAddress | 8] >> (k ^ 7)) & 0x01) << 1 );
                } break;

                case 251: {
                    m_RenderAddress = m_AttributeAddress;
                    m_AttributeByte = m_VRAM[ m_AttributeAddress ];
                    m_AttributeByte &= m_AttributeMask;
                    m_AttributeByte >>= m_AttributeShift;
                    unsigned char color = ( (m_AttributeByte & 0x03) << 2 );
                    for( int k = 0; k != 8; ++k ) m_ColorIndex[ m_CurrentCycle + 13 + k ] = color;
                    if( (m_Reg.R6 & 0x001f) == 0x001f ) m_Reg.R6 ^= 0x041f;
                    else                                m_Reg.R6++;
                    if( (m_Reg.R6 & 0x7000) == 0x7000 ) {
                        unsigned short t = ( m_Reg.R6 & 0x03e0 );
                        m_Reg.R6 &= 0x0fff;
                        switch( t ) {
                            case 0x03a0: m_Reg.R6 ^= 0x0ba0; break;
                            case 0x03e0: m_Reg.R6 ^= 0x03e0; break;
                            default: m_Reg.R6 += 0x0020; break;
                        }
                    } else {
                        m_Reg.R6 += 0x1000;
                    }
                } break;

                case 256: case 258: case 264: case 266: case 272: case 274: case 280: case 282: case 288: case 290: case 296: case 298: case 304:
                case 306: case 312: case 314: {
                    m_RenderAddress = ( 0x2000 | (m_Reg.R6 & 0x0fff) );
                } break;

                case 257: {
                    m_Reg.R6 &= 0xfbe0;
                    m_Reg.R6 |= ( m_LoopyT & 0x041f );
                    m_RenderAddress = ( 0x2000 | (m_Reg.R6 & 0x0fff) );
                } break;

                case 265: case 273: case 281: case 289: case 297: case 305: case 313: {
                    // nada!
                } break;

                case 259: case 267: case 275: case 283: case 291: case 299: case 307: case 315: {
                    // TODO: sprites
                } break;

                case 260: case 268: case 276: case 284: case 292: case 300: case 308: case 316: {
                    m_RenderAddress = m_PatternTableAddress;
                } break;

                case 261: case 269: case 277: case 285: case 293: case 301: case 309: case 317: {
                    // TODO: sprites
                } break;

                case 262: case 270: case 278: case 286: case 294: case 302: case 310: case 318: {
                    m_RenderAddress = ( m_PatternTableAddress | 8 );
                } break;

                case 263: case 271: case 279: case 287: case 295: case 303: case 311: case 319: {
                    // TODO: sprites
                } break;

                case 336: case 338: {
                    m_RenderAddress = ( 0x2000 | (m_Reg.R6 & 0x0fff) );
                } break;

                case 337: case 339: case 340: {
                    // nada!
                } break;
            }

            if( m_RenderPixel && (m_CurrentCycle < 256) ) {
                if( TestBit(m_Reg.R1, 3) && ((m_CurrentCycle >= 8) || TestBit(m_Reg.R1, 1)) ) m_CurrentColorIndex = m_ColorIndex[ m_CurrentCycle + m_FineX ];
                else                                                                          m_CurrentColorIndex = 0;
                // TODO: sprites
                if( ! m_IsRendering ) {
                    if( (m_Reg.R6 & 0x3f00) == 0x3f00 ) m_CurrentColorIndex = ( m_Reg.R6 & 0x1f );
                }
                //m_ScreenData[ m_PixelCounter ] = PALETTE[ m_CurrentColorIndex ];
                m_ScreenData[ (m_Scanline + 1) * 256 + m_CurrentCycle ] = PALETTE[ m_CurrentColorIndex ];
                //printf("\n%i => %lu", m_PixelCounter, m_ScreenData[ m_PixelCounter ]);
            }
        }
        m_PixelCounter++;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
